
package todolist.gui;

import javax.swing.*;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.PlainDocument;
import java.awt.Color;
import java.awt.Container;
import java.awt.Font;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import com.melloware.jintellitype.*;
import todolist.logic.MainController;
import todolist.util.Utils;

/**
 * Application driver class. View class too.
 * HotKey capability requires libraries: jintellitype-1.3.8.jar, JIntellitype.dll, JIntellitype64.dll;
 *	from: http://melloware.com/products/jintellitype/quick-start.html
 * @author Lina
 * @author YouLi
 */
public class GUI {
	
	private static final int COMMAND_MAXLENGTH = 500;
	private static final int ID_CTRL_ALT = 1; // hot key identifier for CTRL_ALT hotkey
	
	private static final Font DISPLAY_FONT = new Font("Lucida Console", Font.PLAIN, 12);
	
	private static final String FILENAME_BACKGROUND = "background.png";
	private static final String FILENAME_APPLICATION_PROPERTIES = "applicationProperties.png";
	private static final String FILENAME_NAVIGATION = "navigation.png";
	private static final String FILENAME_DISPLAY_TASK = "displayTask.png";
	private static final String FILENAME_COMMAND = "command.png";

	public static void main(String[] args) {
		new GUI();
	}
	
	// member GUI attributes
	
	private JLabel _lblApplicationProperties, _lblNavigation, _lblDisplay, _lblCommand, _lblFeedback;
	private JTextArea _txtApplicationPropertiesDisplay, _txtDisplay;
	private JTextPane _txtNavigationDisplay;
	private JScrollPane _objDisplayPane;
	private JTextField _txtCommand;
	private JFrame _objGUIFrame;

	// view --> controller facade
	private MainController _objMC;

	private GUI () {
		_objMC = MainController.getInstance();
		_objMC.init();
		createAndShowGUI();
		refreshAllDisplays();
		initSysTray();
		initHotKeys();
	}
	
	// private init GUI methods
	
	/**
	 * Create, set up, display JFrame and all components within.
	 */
	private void createAndShowGUI() {
		// create and set up the JFrame
		_objGUIFrame = new JFrame("ToDoList Manager");
		_objGUIFrame.setContentPane(new JLabel(createImageIcon(FILENAME_BACKGROUND)));
		_objGUIFrame.getContentPane().setLayout(null);
		_objGUIFrame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
		_objGUIFrame.addWindowListener(new WindowListenerImpl());
		initAllComponents();
		// display the window
		_objGUIFrame.setVisible(true);
		_objGUIFrame.setSize(1200, 700);
		_objGUIFrame.setResizable(false);
		_txtCommand.requestFocusInWindow();
	}

	/**
	 * Create, set up all components.
	 */
	private void initAllComponents() {
		initLabels();
		initApplicationPropertiesDisplay();
		initNavigationDisplay();
		initDisplay();
		initCommand();
		initInputMap();
	}

	/**
	 * Create, set up 5 labels.
	 */
	private void initLabels() {
		ImageIcon imgApplicationProperties = createImageIcon(FILENAME_APPLICATION_PROPERTIES);
		ImageIcon imgNavigation = createImageIcon(FILENAME_NAVIGATION);
		ImageIcon imgDisplay = createImageIcon(FILENAME_DISPLAY_TASK);
		ImageIcon imgCommand = createImageIcon(FILENAME_COMMAND);
		_lblApplicationProperties = new JLabel(imgApplicationProperties);
		_lblNavigation = new JLabel(imgNavigation);
		_lblDisplay = new JLabel(imgDisplay);
		_lblCommand = new JLabel(imgCommand);
		_lblFeedback = new JLabel("Feedback :");
		_lblApplicationProperties.setBounds(30, 40, 331, 28);
		_lblNavigation.setBounds(30, 328, 331, 28);
		_lblDisplay.setBounds(390, 40, 785, 28);
		_lblCommand.setBounds(30, 532, 1150, 28);
		_lblFeedback.setOpaque(true);
		_lblFeedback.setBackground(Color.BLACK);
		_lblFeedback.setForeground(Color.WHITE);
		_lblFeedback.setFont(DISPLAY_FONT);
		_lblFeedback.setBounds(30, 615, 1150, 35);
		Container objContentPane = _objGUIFrame.getContentPane();
		objContentPane.add(_lblApplicationProperties);
		objContentPane.add(_lblNavigation);
		objContentPane.add(_lblDisplay);
		objContentPane.add(_lblCommand);
		objContentPane.add(_lblFeedback);
	}

	/**
	 * Create, set up application properties at top left.
	 */
	private void initApplicationPropertiesDisplay(){
		_txtApplicationPropertiesDisplay = new JTextArea();
		_txtApplicationPropertiesDisplay.setFocusable(false);
		_txtApplicationPropertiesDisplay.setEditable(false);
		_txtApplicationPropertiesDisplay.setLineWrap(true);
		_txtApplicationPropertiesDisplay.setBounds(30, 68, 331, 251);
		_txtApplicationPropertiesDisplay.setFont(DISPLAY_FONT);
		Container objContentPane = _objGUIFrame.getContentPane();
		objContentPane.add(_txtApplicationPropertiesDisplay);
	}

	/**
	 * Create, set up navigation commands at middle left.
	 */
	private void initNavigationDisplay(){
		_txtNavigationDisplay = new JTextPane();
		_txtNavigationDisplay.setFocusable(false);
		_txtNavigationDisplay.setEditable(false);
		_txtNavigationDisplay.setBounds(30, 358, 331, 165);
		_txtNavigationDisplay.setFont(DISPLAY_FONT);
		Container objContentPane = _objGUIFrame.getContentPane();
		objContentPane.add(_txtNavigationDisplay);
	}

	/**
	 * Create, set up display area at top right.
	 */
	private void initDisplay() {
		_txtDisplay = new JTextArea();
		_txtDisplay.setFocusable(false);
		_txtDisplay.setEditable(false);
		_txtDisplay.setLineWrap(true);
		_txtDisplay.setFont(DISPLAY_FONT);
		_objDisplayPane = new JScrollPane(_txtDisplay);
		_objDisplayPane.setBounds(390, 68, 783, 453);
		Container objContentPane = _objGUIFrame.getContentPane();
		objContentPane.add(_objDisplayPane);
	}

	/**
	 * Create, set up command input area at bottom.
	 */
	private void initCommand(){
		_txtCommand = new JTextField();
		_txtCommand.setDocument(new JTextFieldLimit(COMMAND_MAXLENGTH));
		_txtCommand.setFocusable(true);
		_txtCommand.setBounds(30, 557, 1150, 60);
		_txtCommand.setFont(DISPLAY_FONT);
		_txtCommand.addKeyListener(new KeyListenerImpl());
		Container objContentPane = _objGUIFrame.getContentPane();
		objContentPane.add(_txtCommand);
	}

	/**
	 * Set up input map for handling scrolling.
	 */
	private void initInputMap() {
		InputMap objIm = _objDisplayPane.getVerticalScrollBar().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
		objIm.put(KeyStroke.getKeyStroke("DOWN"), "positiveUnitIncrement");
		objIm.put(KeyStroke.getKeyStroke("UP"), "negativeUnitIncrement");
		objIm.put(KeyStroke.getKeyStroke("PAGE_DOWN"), "positiveBlockIncrement");
		objIm.put(KeyStroke.getKeyStroke("PAGE_UP"), "negativeBlockIncrement");
	}

	/**
	 * Creates and returns an image based on given image name.
	 * GUI prompt will already be shown on creation failure.
	 */
	private ImageIcon createImageIcon(String pstrName) {
		return Utils.createImageIcon(pstrName, _objGUIFrame);
	}

	/**
	 * Inner class for setting maxlength of a Text Field by means of a Plain Document.
	 */
	private class JTextFieldLimit extends PlainDocument {
		
		private static final long serialVersionUID = 1L;
		private int _intLimit;

		private JTextFieldLimit(int pintLimit) {
			super();
			_intLimit = pintLimit;
		}

		/**
		 * Set max length.
		 */		
		public void insertString(int pintOffset, String pstrData,
			AttributeSet pobjAttr) throws BadLocationException {
			if (pstrData == null)
				return;
			if ((getLength() + pstrData.length()) <= _intLimit)
				super.insertString(pintOffset, pstrData, pobjAttr);
		}
		
	}
	
	private class RestoreGUIListenerImpl implements RestoreGUIListener {
		@Override
		public void restoreGUI() {
			_objGUIFrame.setVisible(true);
			SystemTrayIcon.getInstance().setTrayIconVisibility(false);
		}
	}

	// private pull data methods

	/**
	 * Pull data for all 5 display areas.
	 */
	private void refreshAllDisplays() {
		_txtApplicationPropertiesDisplay.setText(_objMC.getApplicationProperties());
		_txtNavigationDisplay.setText(_objMC.getNavigation());
		_txtDisplay.setText(_objMC.getDisplay());
		_txtCommand.setText(_objMC.getCommand());
		_lblFeedback.setText(_objMC.getFeedback());
	}
	
	// initialise Tray Icon methods
	
	/**
	 * Initialises tray icon if supported.
	 */
	private void initSysTray() {
		SystemTrayIcon objTray = SystemTrayIcon.getInstance();
		objTray.initSystemTrayIcon();
		objTray.addRestoreGUIActionListener(new RestoreGUIListenerImpl());
		setAppVisibility(true);
	}
	
	/**
	 * Initialises hot keys, only when app is running on Windows OS.
	 */
	private void initHotKeys() {
		if (!Utils.isWindowsOS())
			return;
		if (Utils.is64BitOS())
			JIntellitype.setLibraryLocation(Utils.getApplicationPath() + Utils.getFileSeparator() + "JIntellitype64.dll");
		else
			JIntellitype.setLibraryLocation(Utils.getApplicationPath() + Utils.getFileSeparator() + "JIntellitype.dll");
		JIntellitype.getInstance().addHotKeyListener(new HotKeyListenerImpl());
		JIntellitype.getInstance().registerHotKey(ID_CTRL_ALT, "CTRL+ALT"); // toggle both JFrame and App visibility
	}
	
	// other private methods

	/**
	 * Handles application and Tray Icon visibility.
	 */
	private void setAppVisibility(boolean pblnNewVisibility) {
		_objGUIFrame.setVisible(pblnNewVisibility);
		SystemTrayIcon.getInstance().setTrayIconVisibility(!pblnNewVisibility);
	}
	
	// inner classes

	private class KeyListenerImpl implements KeyListener {
		@Override
		public void keyPressed(KeyEvent pobjEvent) {
			if (pobjEvent.getKeyCode() != KeyEvent.VK_ENTER)
				return;
			// only handle enter keyPressed
			_objMC.executeCommand(_txtCommand.getText());
			refreshAllDisplays();
		}
		/* do nothing for the rest */
		@Override public void keyReleased(KeyEvent pobjEvent) {}
		@Override public void keyTyped(KeyEvent pobjEvent) {}
	}
	
	private class HotKeyListenerImpl implements HotkeyListener {
		@Override
		public void onHotKey(int pintIdentifier) {
			if (pintIdentifier == ID_CTRL_ALT) // toggle app / tray icon visibility
				setAppVisibility(!_objGUIFrame.isVisible());
		}
	}
	
	private class WindowListenerImpl implements WindowListener {
		@Override
		public void windowClosing(WindowEvent pobjEvent) {
			setAppVisibility(false);
		}
		/* do nothing for the rest */
		@Override public void windowActivated(WindowEvent pobjEvent) {}
		@Override public void windowClosed(WindowEvent pobjEvent) {}
		@Override public void windowDeactivated(WindowEvent e) {}
		@Override public void windowDeiconified(WindowEvent e) {}
		@Override public void windowIconified(WindowEvent e) {}
		@Override public void windowOpened(WindowEvent e) {}
	}
	
}
